{
  "nbformat": 4,
  "nbformat_minor": 0,
  "metadata": {
    "colab": {
      "name": "functional.ipynb",
      "provenance": [],
      "private_outputs": true,
      "collapsed_sections": [],
      "toc_visible": true
    },
    "kernelspec": {
      "display_name": "Python 3",
      "language": "python",
      "name": "python3"
    },
    "language_info": {
      "codemirror_mode": {
        "name": "ipython",
        "version": 3
      },
      "file_extension": ".py",
      "mimetype": "text/x-python",
      "name": "python",
      "nbconvert_exporter": "python",
      "pygments_lexer": "ipython3",
      "version": "3.7.3"
    }
  },
  "cells": [
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "-zMKQx6DkKwt"
      },
      "source": [
        "##### Copyright 2019 The TensorFlow Authors."
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "cellView": "form",
        "colab_type": "code",
        "id": "J307vsiDkMMW",
        "colab": {}
      },
      "source": [
        "#@title Licensed under the Apache License, Version 2.0 (the \"License\");\n",
        "# you may not use this file except in compliance with the License.\n",
        "# You may obtain a copy of the License at\n",
        "#\n",
        "# https://www.apache.org/licenses/LICENSE-2.0\n",
        "#\n",
        "# Unless required by applicable law or agreed to in writing, software\n",
        "# distributed under the License is distributed on an \"AS IS\" BASIS,\n",
        "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n",
        "# See the License for the specific language governing permissions and\n",
        "# limitations under the License."
      ],
      "execution_count": 0,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "vCMYwDIE9dTT"
      },
      "source": [
        "# La API funcional \"Keras\" en TensorFlow"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "lAJfkZ-K9flj"
      },
      "source": [
        "<table class=\"tfo-notebook-buttons\" align=\"left\">\n",
        "  <td>\n",
        "    <a target=\"_blank\" href=\"https://www.tensorflow.org/guide/keras/functional\"><img src=\"https://www.tensorflow.org/images/tf_logo_32px.png\" />Ver en TensorFlow.org</a>\n",
        "  </td>\n",
        "  <td>\n",
        "    <a target=\"_blank\" href=\"https://colab.research.google.com/github/tensorflow/docs/blob/master/site/es/guide/keras/functional.ipynb\"><img src=\"https://www.tensorflow.org/images/colab_logo_32px.png\" />Correr en Google Colab</a>\n",
        "  </td>\n",
        "  <td>\n",
        "    <a target=\"_blank\" href=\"https://github.com/tensorflow/docs/blob/master/site/es/guide/keras/functional.ipynb\"><img src=\"https://www.tensorflow.org/images/GitHub-Mark-32px.png\" />Ver código fuente en GitHub</a>\n",
        "  </td>\n",
        "  <td>\n",
        "    <a href=\"https://storage.googleapis.com/tensorflow_docs/docs/site/es/guide/keras/functional.ipynb\"><img src=\"https://www.tensorflow.org/images/download_logo_32px.png\" />Descargar notebook</a>\n",
        "  </td>\n",
        "</table>"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "dt4DDXp2Vca-",
        "colab_type": "text"
      },
      "source": [
        "Note: Nuestra comunidad de Tensorflow ha traducido estos documentos. Como las traducciones de la comunidad\n",
        "son basados en el \"mejor esfuerzo\", no hay ninguna garantia que esta sea un reflejo preciso y actual \n",
        "de la [Documentacion Oficial en Ingles](https://www.tensorflow.org/?hl=en).\n",
        "Si tienen sugerencias sobre como mejorar esta traduccion, por favor envian un \"Pull request\"\n",
        "al siguiente repositorio [tensorflow/docs](https://github.com/tensorflow/docs).\n",
        "Para ofrecerse como voluntario o hacer revision de las traducciones de la Comunidad\n",
        "por favor contacten al siguiente grupo [docs@tensorflow.org list](https://groups.google.com/a/tensorflow.org/forum/#!forum/docs)."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "ITh3wzORxgpw"
      },
      "source": [
        "## Setup"
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "colab_type": "code",
        "id": "HFbM9dcfxh4l",
        "colab": {}
      },
      "source": [
        "from __future__ import absolute_import, division, print_function, unicode_literals\n",
        "\n",
        "try:\n",
        "  # %tensorflow_version de Colab\n",
        "  %tensorflow_version 2.x\n",
        "except Exception:\n",
        "  pass\n",
        "import tensorflow as tf\n",
        "\n",
        "tf.keras.backend.clear_session()  # Reseteo sencillo"
      ],
      "execution_count": 0,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "ZI47-lpfkZ5c"
      },
      "source": [
        "\n",
        "## Introduccion\n",
        "\n",
        "Ya estás familiarizado con el uso del metodo `keras.Sequential()` para crear modelos.\n",
        "La API funcional es una forma de crear modelos mas dinamicos que con ` Sequential `: La API funcional puede manejar modelos con topología no lineal, modelos con capas compartidas y modelos con múltiples entradas o salidas.\n",
        "\n",
        "Se basa en la idea de que un modelo de aprendizaje profundo\n",
        "suele ser un gráfico acíclico dirigido (DAG) de capas.\n",
        "La API funcional es un conjunto de herramientas para **construir gráficos de capas**.\n",
        "\n",
        "Considera el siguiente modelo:\n",
        "\n",
        "```\n",
        "(input: 784-vectores dimensionales)\n",
        "       ↧\n",
        "[Dense (64 units, activacion relu)]\n",
        "       ↧\n",
        "[Dense (64 units, activacion relu)]\n",
        "       ↧\n",
        "[Dense (10 units, activacion softmax)]\n",
        "       ↧\n",
        "(output: distribución de probabilidad en 10 clases)\n",
        "```\n",
        "\n",
        "Es una simple grafica de tres capas.\n",
        "\n",
        "Para construir este modelo con la API funcional,\n",
        "comenzarías creando un nodo de entrada:"
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "colab_type": "code",
        "id": "Yxi0LaSHkDT-",
        "colab": {}
      },
      "source": [
        "from tensorflow import keras\n",
        "\n",
        "inputs = keras.Input(shape=(784,))"
      ],
      "execution_count": 0,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "Mr3Z_Pxcnf-H"
      },
      "source": [
        "Aqui solo especificamos el tipo de nuestra data set: 784-vectores dimensionales.\n",
        "Nota que el tamaño del batch siempre debe ser omitido, solo se incluye el tipo de la data set.\n",
        "Para una input de tipo imágen ` (31,32,3) ` hubiese sido:"
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "colab_type": "code",
        "id": "0-2Q2nJNneIO",
        "colab": {}
      },
      "source": [
        "img_inputs = keras.Input(shape=(32, 32, 3))"
      ],
      "execution_count": 0,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "HoMFNu-pnkgF"
      },
      "source": [
        "Lo que se devuelve, ` input `, contiene información sobre la forma y el tipo de dato que se espera ingresa en tu modelo:"
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "colab_type": "code",
        "id": "ddIr9LPJnibj",
        "colab": {}
      },
      "source": [
        "inputs.shape"
      ],
      "execution_count": 0,
      "outputs": []
    },
    {
      "cell_type": "code",
      "metadata": {
        "colab_type": "code",
        "id": "lZkLJeQonmTe",
        "colab": {}
      },
      "source": [
        "inputs.dtype"
      ],
      "execution_count": 0,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "kZnhhndTnrzC"
      },
      "source": [
        "Puedes crear un nuevo nodo en el grafico de capas mandando a llamar al objeto ` input `."
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "colab_type": "code",
        "id": "sMyyMTqDnpYV",
        "colab": {}
      },
      "source": [
        "from tensorflow.keras import layers\n",
        "\n",
        "dense = layers.Dense(64, activation='relu')\n",
        "x = dense(inputs)"
      ],
      "execution_count": 0,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "besm-lgFnveV"
      },
      "source": [
        "La acción \"layer call\" es como dibujar una flecha desde \"entradas\" a la capa que creamos.\n",
        "Estamos \"pasando\" las entradas a la capa `dense`, y afuera obtenemos` x`.\n",
        "\n",
        "Agreguemos algunas capas más a nuestro gráfico de capas:\n",
        "\n",
        "La acción \"llamada a la capa\" es como dibujar una flecha de \"entradas\" a la capa que creamos.\n",
        "\n",
        "Estamos pasando las entradas a una capa mas densa, y respecto a la salida obtenemos una ` x `."
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "colab_type": "code",
        "id": "DbF-MIO2ntf7",
        "colab": {}
      },
      "source": [
        "x = layers.Dense(64, activation='relu')(x)\n",
        "outputs = layers.Dense(10, activation='softmax')(x)"
      ],
      "execution_count": 0,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "B38UlEIlnz_8"
      },
      "source": [
        "LLegados a este punto, podemos crear un ` Modelo ` especificando sus entradas y salidas en las capas de graficas."
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "colab_type": "code",
        "id": "MrSfwvl-nx9s",
        "colab": {}
      },
      "source": [
        "model = keras.Model(inputs=inputs, outputs=outputs)"
      ],
      "execution_count": 0,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "5EeeV1xJn3jW"
      },
      "source": [
        "Recapitulando, esta es nuestra definción completa del proceso:"
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "colab_type": "code",
        "id": "xkz7oqj2n1-q",
        "colab": {}
      },
      "source": [
        "inputs = keras.Input(shape=(784,), name='img')\n",
        "x = layers.Dense(64, activation='relu')(inputs)\n",
        "x = layers.Dense(64, activation='relu')(x)\n",
        "outputs = layers.Dense(10, activation='softmax')(x)\n",
        "\n",
        "model = keras.Model(inputs=inputs, outputs=outputs, name='mnist_model')"
      ],
      "execution_count": 0,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "jJzocCbdn6qj"
      },
      "source": [
        "Veamos como se muestra el model summary:"
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "colab_type": "code",
        "id": "GirC9odQn5Ep",
        "colab": {}
      },
      "source": [
        "model.summary()"
      ],
      "execution_count": 0,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "mbNqYAlOn-vA"
      },
      "source": [
        "También podemos trazar el modelo como un gráfico:"
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "colab_type": "code",
        "id": "JYh2wLain8Oi",
        "colab": {}
      },
      "source": [
        "keras.utils.plot_model(model, 'my_first_model.png')"
      ],
      "execution_count": 0,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "QtgX2RoGoDZo"
      },
      "source": [
        "Y opcionalmente mostrar la entrada y la salida de la forma de cada capa en la gráfica ploteada:"
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "colab_type": "code",
        "id": "7FGesSSuoAG5",
        "colab": {}
      },
      "source": [
        "keras.utils.plot_model(model, 'my_first_model_with_shape_info.png', show_shapes=True)"
      ],
      "execution_count": 0,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "PBZ9XE6LoWvi"
      },
      "source": [
        "Esta figura y el código que escribimos son prácticamente idénticos. En la versión de código, las flechas de conexión simplemente se reemplazan por la operación de llamada.\n",
        "\n",
        "Un \"gráfico de capas\" es una imagen mental muy intuitiva para un modelo de aprendizaje profundo, y la API funcional es una forma de crear modelos que reflejan de cerca esta imagen mental.\n"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "WUUHMaKLoZDn"
      },
      "source": [
        "\n",
        "\n",
        "## Entrenamiento, evaluación e inferencia.\n",
        "\n",
        "El entrenamiento, la evaluación y la inferencia funcionan exactamente de la misma manera para los modelos construidos\n",
        "utilizando la API funcional como para los modelos secuenciales.\n",
        "\n",
        "Aquí hay una demostración rápida.\n",
        "\n",
        "Aquí cargamos datos de imagen MNIST, los rediseñamos en vectores,\n",
        "ajustar el modelo en los datos (mientras se monitorea el rendimiento en una división de validación),\n",
        "y finalmente evaluamos nuestro modelo en los datos de prueba:"
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "colab_type": "code",
        "id": "DnHvkD22oFEY",
        "colab": {}
      },
      "source": [
        "(x_train, y_train), (x_test, y_test) = keras.datasets.mnist.load_data()\n",
        "x_train = x_train.reshape(60000, 784).astype('float32') / 255\n",
        "x_test = x_test.reshape(10000, 784).astype('float32') / 255\n",
        "\n",
        "model.compile(loss='sparse_categorical_crossentropy',\n",
        "              optimizer=keras.optimizers.RMSprop(),\n",
        "              metrics=['accuracy'])\n",
        "history = model.fit(x_train, y_train,\n",
        "                    batch_size=64,\n",
        "                    epochs=5,\n",
        "                    validation_split=0.2)\n",
        "test_scores = model.evaluate(x_test, y_test, verbose=2)\n",
        "print('Test loss:', test_scores[0])\n",
        "print('Test accuracy:', test_scores[1])"
      ],
      "execution_count": 0,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "c3nq2fjiLCkE"
      },
      "source": [
        "Para obtener una guía completa sobre el entrenamiento y evaluación de modelos, consulta [Guía de entrenamiento y evaluación](./train_and_evaluate.ipynb)."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "XOsL56zDorLh"
      },
      "source": [
        "## Almacenado y serialización\n",
        "\n",
        "El almacenado y la serialización funcionan exactamente de la misma manera para los modelos construidos\n",
        "utilizando la API funcional como para los modelos secuenciales.\n",
        "\n",
        "Una forma estándar de guardar un modelo funcional es llamar a `model.save ()` para guardar todo el modelo en un solo archivo.\n",
        "Posteriormente, puede volver a crear el mismo modelo a partir de este archivo, incluso si ya no tiene acceso al código.\n",
        "eso creó el modelo.\n",
        "\n",
        "Este archivo incluye:\n",
        "- La arquitectura del modelo.\n",
        "- Los valores de peso del modelo (que se aprendieron durante el entrenamiento)\n",
        "- La configuración de entrenamiento del modelo (lo que pasó a `compilar`), si corresponde\n",
        "- El optimizador y su estado, si corresponde (esto le permite reiniciar el entrenamiento donde lo dejó)"
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "colab_type": "code",
        "id": "kN-AO7xvobtr",
        "colab": {}
      },
      "source": [
        "model.save('path_to_my_model.h5')\n",
        "del model\n",
        "# Recrea el mismo modelo, desde el archivo:\n",
        "model = keras.models.load_model('path_to_my_model.h5')"
      ],
      "execution_count": 0,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "u0J0tFPHK4pb"
      },
      "source": [
        "Para obtener una guía completa sobre el guardado de modelos, consulta [Guía para guardar y serializar modelos](./save_and_serialize.ipynb)."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "lKz1WWr2LUzF"
      },
      "source": [
        "## Usando el mismo gráfico de capas para definir múltiples modelos\n",
        "\n",
        "\n",
        "En la API funcional, los modelos se crean especificando sus entradas\n",
        "y salidas en un gráfico de capas. Eso significa que un solo gráfico de capas\n",
        "Se puede utilizar para generar múltiples modelos.\n",
        "\n",
        "En el siguiente ejemplo, usamos la misma arquitectura de capas para crear instancias de dos modelos:\n",
        "un modelo de \"codificador\" que convierte las entradas de imagen en vectores de 16 dimensiones,\n",
        "y un modelo completo de `autoencoder` para entrenamiento.\n"
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "colab_type": "code",
        "id": "WItZQr6LuVbF",
        "colab": {}
      },
      "source": [
        "encoder_input = keras.Input(shape=(28, 28, 1), name='img')\n",
        "x = layers.Conv2D(16, 3, activation='relu')(encoder_input)\n",
        "x = layers.Conv2D(32, 3, activation='relu')(x)\n",
        "x = layers.MaxPooling2D(3)(x)\n",
        "x = layers.Conv2D(32, 3, activation='relu')(x)\n",
        "x = layers.Conv2D(16, 3, activation='relu')(x)\n",
        "encoder_output = layers.GlobalMaxPooling2D()(x)\n",
        "\n",
        "encoder = keras.Model(encoder_input, encoder_output, name='encoder')\n",
        "encoder.summary()\n",
        "\n",
        "x = layers.Reshape((4, 4, 1))(encoder_output)\n",
        "x = layers.Conv2DTranspose(16, 3, activation='relu')(x)\n",
        "x = layers.Conv2DTranspose(32, 3, activation='relu')(x)\n",
        "x = layers.UpSampling2D(3)(x)\n",
        "x = layers.Conv2DTranspose(16, 3, activation='relu')(x)\n",
        "decoder_output = layers.Conv2DTranspose(1, 3, activation='relu')(x)\n",
        "\n",
        "autoencoder = keras.Model(encoder_input, decoder_output, name='autoencoder')\n",
        "autoencoder.summary()"
      ],
      "execution_count": 0,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "oNeg3WWFuYZK"
      },
      "source": [
        "Tenga en cuenta que hacemos que la arquitectura de decodificación sea estrictamente simétrica a la arquitectura de codificación,\n",
        "para que obtengamos una forma de salida que sea igual a la forma de entrada `(28, 28, 1)`.\n",
        "El reverso de una capa `Conv2D` es una capa` Conv2DTranspose`, y el reverso de una capa `MaxPooling2D`\n",
        "La capa es una capa `UpSampling2D`."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "h1FVW4j-uc6Y"
      },
      "source": [
        "\n",
        "## Todos los modelos son invocables, al igual que las capas.\n",
        "\n",
        "Puede tratar cualquier modelo como si fuera una capa, llamándolo en una `Entrada` o en la salida de otra capa.\n",
        "Tenga en cuenta que al llamar a un modelo no solo está reutilizando la arquitectura del modelo, también está reutilizando sus pesos.\n",
        "\n",
        "Veamos esto en acción. Aquí hay una versión diferente del ejemplo de autoencoder que crea un modelo de codificador, un modelo de decodificador,\n",
        "y encadenarlos en dos llamadas para obtener el modelo de autoencoder:"
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "colab_type": "code",
        "id": "Ld7KdsQ_uZbr",
        "colab": {}
      },
      "source": [
        "encoder_input = keras.Input(shape=(28, 28, 1), name='original_img')\n",
        "x = layers.Conv2D(16, 3, activation='relu')(encoder_input)\n",
        "x = layers.Conv2D(32, 3, activation='relu')(x)\n",
        "x = layers.MaxPooling2D(3)(x)\n",
        "x = layers.Conv2D(32, 3, activation='relu')(x)\n",
        "x = layers.Conv2D(16, 3, activation='relu')(x)\n",
        "encoder_output = layers.GlobalMaxPooling2D()(x)\n",
        "\n",
        "encoder = keras.Model(encoder_input, encoder_output, name='encoder')\n",
        "encoder.summary()\n",
        "\n",
        "decoder_input = keras.Input(shape=(16,), name='encoded_img')\n",
        "x = layers.Reshape((4, 4, 1))(decoder_input)\n",
        "x = layers.Conv2DTranspose(16, 3, activation='relu')(x)\n",
        "x = layers.Conv2DTranspose(32, 3, activation='relu')(x)\n",
        "x = layers.UpSampling2D(3)(x)\n",
        "x = layers.Conv2DTranspose(16, 3, activation='relu')(x)\n",
        "decoder_output = layers.Conv2DTranspose(1, 3, activation='relu')(x)\n",
        "\n",
        "decoder = keras.Model(decoder_input, decoder_output, name='decoder')\n",
        "decoder.summary()\n",
        "\n",
        "autoencoder_input = keras.Input(shape=(28, 28, 1), name='img')\n",
        "encoded_img = encoder(autoencoder_input)\n",
        "decoded_img = decoder(encoded_img)\n",
        "autoencoder = keras.Model(autoencoder_input, decoded_img, name='autoencoder')\n",
        "autoencoder.summary()"
      ],
      "execution_count": 0,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "icQFny_huiXC"
      },
      "source": [
        "Como puede ver, el modelo puede estar anidado: un modelo puede contener submodelos (ya que un modelo es como una capa).\n",
        "\n",
        "Un caso de uso común para la anidación de modelos es * ensamblaje *.\n",
        "Como ejemplo, a continuación se explica cómo agrupar un conjunto de modelos en un solo modelo que promedia sus predicciones:"
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "colab_type": "code",
        "id": "ZBlZbRn5uk-9",
        "colab": {}
      },
      "source": [
        "def get_model():\n",
        "  inputs = keras.Input(shape=(128,))\n",
        "  outputs = layers.Dense(1, activation='sigmoid')(inputs)\n",
        "  return keras.Model(inputs, outputs)\n",
        "\n",
        "model1 = get_model()\n",
        "model2 = get_model()\n",
        "model3 = get_model()\n",
        "\n",
        "inputs = keras.Input(shape=(128,))\n",
        "y1 = model1(inputs)\n",
        "y2 = model2(inputs)\n",
        "y3 = model3(inputs)\n",
        "outputs = layers.average([y1, y2, y3])\n",
        "ensemble_model = keras.Model(inputs=inputs, outputs=outputs)"
      ],
      "execution_count": 0,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "e1za1TZxuoId"
      },
      "source": [
        "## Manipulación de topologías gráficas complejas\n",
        "\n",
        "\n",
        "### Modelos con múltiples entradas y salidas\n",
        "\n",
        "La API funcional facilita la manipulación de múltiples entradas y salidas.\n",
        "Esto no se puede manejar con la API secuencial.\n",
        "\n",
        "Aquí hay un ejemplo simple.\n",
        "\n",
        "Supongamos que está creando un sistema para clasificar los tickets de emisión personalizados por prioridad y enrutarlos al departamento correcto.\n",
        "\n",
        "Tu modelo tendrá 3 entradas:\n",
        "\n",
        "- Título del ticket (entrada de texto)\n",
        "- Cuerpo del texto del ticket (entrada de texto)\n",
        "- Cualquier etiqueta agregada por el usuario (entrada categórica)\n",
        "\n",
        "Tendrá dos salidas:\n",
        "\n",
        "- Puntuación de prioridad entre 0 y 1 (salida sigmoidea escalar)\n",
        "- El departamento que debe manejar el ticket (salida softmax sobre el conjunto de departamentos)\n",
        "\n",
        "Construyamos este modelo en pocas líneas con la API funcional."
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "colab_type": "code",
        "id": "Gt91OtzbutJy",
        "colab": {}
      },
      "source": [
        "num_tags = 12  # Número de etiquetas de problemas únicos\n",
        "num_words = 10000  # Tamaño del vocabulario obtenido al preprocesar datos de texto\n",
        "num_departments = 4  # Número de departamentos para predicciones.\n",
        "\n",
        "title_input = keras.Input(shape=(None,), name='title')  # Secuencia de longitud variable de entradas\n",
        "body_input = keras.Input(shape=(None,), name='body')  # Secuencia de longitud variable de entradas\n",
        "tags_input = keras.Input(shape=(num_tags,), name='tags')  # Vectores binarios de tamaño `num_tags`\n",
        "\n",
        "# Ingresa cada palabra en el título en un vector de 64 dimensiones\n",
        "title_features = layers.Embedding(num_words, 64)(title_input)\n",
        "# Ingresa cada palabra en el texto en un vector de 64 dimensiones\n",
        "body_features = layers.Embedding(num_words, 64)(body_input)\n",
        "\n",
        "# Reduce la secuencia de palabras ingresadas en el título en un solo vector de 128 dimensiones\n",
        "title_features = layers.LSTM(128)(title_features)\n",
        "# Reduce la secuencia de palabras ingresadas en el cuerpo en un solo vector de 32 dimensiones\n",
        "body_features = layers.LSTM(32)(body_features)\n",
        "\n",
        "# Combina todas las funciones disponibles en un solo vector grande mediante concatenación\n",
        "x = layers.concatenate([title_features, body_features, tags_input])\n",
        "\n",
        "# Pegua una regresión logística para la predicción de prioridad en la parte superior de las características\n",
        "priority_pred = layers.Dense(1, activation='sigmoid', name='priority')(x)\n",
        "# Stick a department classifier on top of the features\n",
        "department_pred = layers.Dense(num_departments, activation='softmax', name='department')(x)\n",
        "\n",
        "# Instancia un modelo de extremo a extremo que prediga tanto la prioridad como el departamento\n",
        "model = keras.Model(inputs=[title_input, body_input, tags_input],\n",
        "                    outputs=[priority_pred, department_pred])"
      ],
      "execution_count": 0,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "KIS7lqW0uwh-"
      },
      "source": [
        "Ploteando el modelo:"
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "colab_type": "code",
        "id": "IMij4gzhuzYV",
        "colab": {}
      },
      "source": [
        "keras.utils.plot_model(model, 'multi_input_and_output_model.png', show_shapes=True)"
      ],
      "execution_count": 0,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "oOyuig2Hu00p"
      },
      "source": [
        "Al compilar este modelo, podemos asignar diferentes pérdidas a cada salida.\n",
        "Incluso puede asignar diferentes pesos a cada pérdida, para modular su\n",
        "contribución a la pérdida total de entrenamiento."
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "colab_type": "code",
        "id": "Crtdpi5Uu2cX",
        "colab": {}
      },
      "source": [
        "model.compile(optimizer=keras.optimizers.RMSprop(1e-3),\n",
        "              loss=['binary_crossentropy', 'categorical_crossentropy'],\n",
        "              loss_weights=[1., 0.2])"
      ],
      "execution_count": 0,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "t42Jrn0Yu5jL"
      },
      "source": [
        "Como dimos nombres a nuestras capas de salida, también podríamos especificar la pérdida de esta manera:"
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "colab_type": "code",
        "id": "dPM0EwW_u6mV",
        "colab": {}
      },
      "source": [
        "model.compile(optimizer=keras.optimizers.RMSprop(1e-3),\n",
        "              loss={'priority': 'binary_crossentropy',\n",
        "                    'department': 'categorical_crossentropy'},\n",
        "              loss_weights=[1., 0.2])"
      ],
      "execution_count": 0,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "bpTx2sXnu3-W"
      },
      "source": [
        "Podemos entrenar el modelo pasando listas de matrices Numpy de entradas y objetivos:"
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "colab_type": "code",
        "id": "nB-upOoGu_k4",
        "colab": {}
      },
      "source": [
        "import numpy as np\n",
        "\n",
        "# Datos de entrada ficticios\n",
        "title_data = np.random.randint(num_words, size=(1280, 10))\n",
        "body_data = np.random.randint(num_words, size=(1280, 100))\n",
        "tags_data = np.random.randint(2, size=(1280, num_tags)).astype('float32')\n",
        "# Datos objetivo ficticios\n",
        "priority_targets = np.random.random(size=(1280, 1))\n",
        "dept_targets = np.random.randint(2, size=(1280, num_departments))\n",
        "\n",
        "model.fit({'title': title_data, 'body': body_data, 'tags': tags_data},\n",
        "          {'priority': priority_targets, 'department': dept_targets},\n",
        "          epochs=2,\n",
        "          batch_size=32)"
      ],
      "execution_count": 0,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "qNguhBWuvCtz"
      },
      "source": [
        "Al llamar al ajuste con un objeto `Dataset`, debería producir un\n",
        "tupla de listas como `([title_data, body_data, tags_data], [priority_targets, dept_targets])`\n",
        "o una tupla de diccionarios como\n",
        "`({'title': title_data, 'body': body_data, 'tags': tags_data}, {'priority': priority_targets, 'department': dept_targets})`.\n",
        "\n",
        "Para obtener una explicación más detallada, consulta la guía completa [Guía de entrenamiento y evaluación](./train_and_evaluate.ipynb)."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "tR0X5tTOvPyg"
      },
      "source": [
        "### Un modelo de Red neuronal residual de juguete\n",
        "\n",
        "Además de los modelos con múltiples entradas y salidas,\n",
        "La API funcional facilita la manipulación de topologías de conectividad no lineal,\n",
        "es decir, modelos donde las capas no están conectadas secuencialmente.\n",
        "Esto tampoco se puede manejar con la API secuencial (como su nombre lo indica).\n",
        "\n",
        "Un caso de uso común para esto son las conexiones residuales.\n",
        "\n",
        "Construyamos un modelo de ResNet de juguete para CIFAR10 para demostrar esto."
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "colab_type": "code",
        "id": "VzMoYrMNvXrm",
        "colab": {}
      },
      "source": [
        "inputs = keras.Input(shape=(32, 32, 3), name='img')\n",
        "x = layers.Conv2D(32, 3, activation='relu')(inputs)\n",
        "x = layers.Conv2D(64, 3, activation='relu')(x)\n",
        "block_1_output = layers.MaxPooling2D(3)(x)\n",
        "\n",
        "x = layers.Conv2D(64, 3, activation='relu', padding='same')(block_1_output)\n",
        "x = layers.Conv2D(64, 3, activation='relu', padding='same')(x)\n",
        "block_2_output = layers.add([x, block_1_output])\n",
        "\n",
        "x = layers.Conv2D(64, 3, activation='relu', padding='same')(block_2_output)\n",
        "x = layers.Conv2D(64, 3, activation='relu', padding='same')(x)\n",
        "block_3_output = layers.add([x, block_2_output])\n",
        "\n",
        "x = layers.Conv2D(64, 3, activation='relu')(block_3_output)\n",
        "x = layers.GlobalAveragePooling2D()(x)\n",
        "x = layers.Dense(256, activation='relu')(x)\n",
        "x = layers.Dropout(0.5)(x)\n",
        "outputs = layers.Dense(10, activation='softmax')(x)\n",
        "\n",
        "model = keras.Model(inputs, outputs, name='toy_resnet')\n",
        "model.summary()"
      ],
      "execution_count": 0,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "ISQX32bgrkis"
      },
      "source": [
        "Ploteando el modelo:"
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "colab_type": "code",
        "id": "pNFVkAd3rlCM",
        "colab": {}
      },
      "source": [
        "keras.utils.plot_model(model, 'mini_resnet.png', show_shapes=True)"
      ],
      "execution_count": 0,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "ECcG87yZrxp5"
      },
      "source": [
        "Vamos a entrenarlo:"
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "colab_type": "code",
        "id": "_iXGz5XEryou",
        "colab": {}
      },
      "source": [
        "(x_train, y_train), (x_test, y_test) = keras.datasets.cifar10.load_data()\n",
        "x_train = x_train.astype('float32') / 255.\n",
        "x_test = x_test.astype('float32') / 255.\n",
        "y_train = keras.utils.to_categorical(y_train, 10)\n",
        "y_test = keras.utils.to_categorical(y_test, 10)\n",
        "\n",
        "model.compile(optimizer=keras.optimizers.RMSprop(1e-3),\n",
        "              loss='categorical_crossentropy',\n",
        "              metrics=['acc'])\n",
        "model.fit(x_train, y_train,\n",
        "          batch_size=64,\n",
        "          epochs=1,\n",
        "          validation_split=0.2)"
      ],
      "execution_count": 0,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "XQfg0JUkr7SH"
      },
      "source": [
        "## Compartir capas\n",
        "\n",
        "Otro buen uso de la API funcional son los modelos que usan capas compartidas. Las capas compartidas son instancias de capa que se reutilizan varias veces en un mismo modelo: aprenden características que corresponden a múltiples rutas en el gráfico de capas.\n",
        "\n",
        "Las capas compartidas a menudo se usan para codificar entradas que provienen de espacios similares (por ejemplo, dos piezas de texto diferentes que presentan un vocabulario similar), ya que permiten compartir información entre estas diferentes entradas y hacen posible entrenar un modelo de este tipo en menos datos. Si se ve una palabra determinada en una de las entradas, eso beneficiará el procesamiento de todas las entradas que pasan por la capa compartida.\n",
        "\n",
        "Para compartir una capa en la API funcional, simplemente llame a la misma instancia de capa varias veces. Por ejemplo, aquí hay una capa `Ingresa (del ingles Embedding)` compartida entre dos entradas de texto diferentes:"
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "colab_type": "code",
        "id": "R9pAPQCnKuMR",
        "colab": {}
      },
      "source": [
        "# Ingreso de 1000 palabras únicas asignadas a vectores de 128 dimensiones\n",
        "shared_embedding = layers.Embedding(1000, 128)\n",
        "\n",
        "# Secuencia de longitud variable de enteros\n",
        "text_input_a = keras.Input(shape=(None,), dtype='int32')\n",
        "\n",
        "# Secuencia de longitud variable de enteros\n",
        "text_input_b = keras.Input(shape=(None,), dtype='int32')\n",
        "\n",
        "# Reutilizamos la misma capa para codificar ambas entradas\n",
        "encoded_input_a = shared_embedding(text_input_a)\n",
        "encoded_input_b = shared_embedding(text_input_b)"
      ],
      "execution_count": 0,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "xNEKvfUpr-Kf"
      },
      "source": [
        "## Extracción y reutilización de nodos en el gráfico de capas"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "JHVGI6bEr-ze"
      },
      "source": [
        "Debido a que el gráfico de capas que está manipulando en la API funcional es una estructura de datos estática, se puede acceder e inspeccionarlo. Así es como podemos trazar modelos funcionales como imágenes, por ejemplo.\n",
        "\n",
        "Esto también significa que podemos acceder a las activaciones de capas intermedias (\"nodos\" en el gráfico) y reutilizarlas en otros lugares. ¡Esto es extremadamente útil para la extracción de características, por ejemplo!\n",
        "\n",
        "Veamos un ejemplo. Este es un modelo VGG19 con pesas pre-entrenadas en ImageNet:"
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "colab_type": "code",
        "id": "c-gl3xHBH-oX",
        "colab": {}
      },
      "source": [
        "from tensorflow.keras.applications import VGG19\n",
        "\n",
        "vgg19 = VGG19()"
      ],
      "execution_count": 0,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "AKefin_xIGBP"
      },
      "source": [
        "Y estas son las activaciones intermedias del modelo, obtenidas al consultar la estructura de datos del gráfico:"
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "colab_type": "code",
        "id": "1_Ap05fgIRgE",
        "colab": {}
      },
      "source": [
        "features_list = [layer.output for layer in vgg19.layers]"
      ],
      "execution_count": 0,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "H1zx5qM7IYu4"
      },
      "source": [
        "Podemos usar estas características para crear un nuevo modelo de extracción de características, que devuelve los valores de las activaciones de la capa intermedia, y podemos hacer todo esto en 3 líneas."
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "colab_type": "code",
        "id": "NrU82Pa8Igwo",
        "colab": {}
      },
      "source": [
        "feat_extraction_model = keras.Model(inputs=vgg19.input, outputs=features_list)\n",
        "\n",
        "img = np.random.random((1, 224, 224, 3)).astype('float32')\n",
        "extracted_features = feat_extraction_model(img)"
      ],
      "execution_count": 0,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "G-e2-jNCLIqy"
      },
      "source": [
        "Esto es útil cuando [implementa la transferencia de estilo neural] (https://medium.com/tensorflow/neural-style-transfer-creating-art-with-deep-learning-using-tf-keras-and-eager-execution- 7d541ac31398), entre otras cosas."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "t9M2Uvi3sBy0"
      },
      "source": [
        "## Extendiendo la API escribiendo capas personalizadas\n",
        "\n",
        "tf.keras tiene una amplia gama de capas incorporadas. Aquí están algunos ejemplos:\n",
        "\n",
        "- Capas convolucionales: `Conv1D`,` Conv2D`, `Conv3D`,` Conv2DTranspose`, etc.\n",
        "- Capas de agrupación: `MaxPooling1D`,` MaxPooling2D`, `MaxPooling3D`,` AveragePooling1D`, etc.\n",
        "- Capas RNN: `GRU`,` LSTM`, `ConvLSTM2D`, etc.\n",
        "- `BatchNormalization`,` Dropout`, `Embedded`, etc.\n",
        "\n",
        "Si no encuentras lo que necesitas, es fácil extender la API creando tus propias capas.\n",
        "\n",
        "Todas las capas subclasifican la clase `Layer` e implementan:\n",
        "- Un método `call`, que especifica el cálculo realizado por la capa.\n",
        "- Un método `build`, que crea los pesos de la capa (tenga en cuenta que esto es solo una convención de estilo; también puede crear pesos en` __init__`).\n",
        "\n",
        "Para obtener más información sobre cómo crear capas desde cero, consulta la guía [Guía para escribir capas y modelos desde cero](./custom_layers_and_models.ipynb).\n",
        "\n",
        "Aquí hay una implementación simple de una capa `Densa`:"
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "colab_type": "code",
        "id": "ztAmarbgNV6V",
        "colab": {}
      },
      "source": [
        "class CustomDense(layers.Layer):\n",
        "\n",
        "  def __init__(self, units=32):\n",
        "    super(CustomDense, self).__init__()\n",
        "    self.units = units\n",
        "\n",
        "  def build(self, input_shape):\n",
        "    self.w = self.add_weight(shape=(input_shape[-1], self.units),\n",
        "                             initializer='random_normal',\n",
        "                             trainable=True)\n",
        "    self.b = self.add_weight(shape=(self.units,),\n",
        "                             initializer='random_normal',\n",
        "                             trainable=True)\n",
        "\n",
        "  def call(self, inputs):\n",
        "    return tf.matmul(inputs, self.w) + self.b\n",
        "\n",
        "inputs = keras.Input((4,))\n",
        "outputs = CustomDense(10)(inputs)\n",
        "\n",
        "model = keras.Model(inputs, outputs)"
      ],
      "execution_count": 0,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "NXxp_32bNWTy"
      },
      "source": [
        "Si deseas que tu capa personalizada admita la serialización, también debes definir un método `get_config`,\n",
        "que devuelve los argumentos del constructor de la instancia de capa:"
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "colab_type": "code",
        "id": "K3OQ4XxzNfAZ",
        "colab": {}
      },
      "source": [
        "class CustomDense(layers.Layer):\n",
        "\n",
        "  def __init__(self, units=32):\n",
        "    super(CustomDense, self).__init__()\n",
        "    self.units = units\n",
        "\n",
        "  def build(self, input_shape):\n",
        "    self.w = self.add_weight(shape=(input_shape[-1], self.units),\n",
        "                             initializer='random_normal',\n",
        "                             trainable=True)\n",
        "    self.b = self.add_weight(shape=(self.units,),\n",
        "                             initializer='random_normal',\n",
        "                             trainable=True)\n",
        "\n",
        "  def call(self, inputs):\n",
        "    return tf.matmul(inputs, self.w) + self.b\n",
        "\n",
        "  def get_config(self):\n",
        "    return {'units': self.units}\n",
        "\n",
        "\n",
        "inputs = keras.Input((4,))\n",
        "outputs = CustomDense(10)(inputs)\n",
        "\n",
        "model = keras.Model(inputs, outputs)\n",
        "config = model.get_config()\n",
        "\n",
        "new_model = keras.Model.from_config(\n",
        "    config, custom_objects={'CustomDense': CustomDense})"
      ],
      "execution_count": 0,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "kXg6hZN_NfN8"
      },
      "source": [
        "Opcionalmente, también podría implementar el método de clase `from_config (cls, config)`, que se encarga de recrear una instancia de capa dado su diccionario de configuración. La implementación predeterminada de `from_config` es:\n",
        "\n",
        "```python\n",
        "def from_config(cls, config):\n",
        "  return cls(**config)\n",
        "```"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "ifOVqn84sCNU"
      },
      "source": [
        "## Cuándo usar la API funcional\n",
        "\n",
        "¿Cómo decidir si usar la API funcional para crear un nuevo modelo o simplemente subclasificar la clase `Modelo` directamente?\n",
        "\n",
        "En general, la API funcional es de nivel superior, más fácil y segura de usar, y tiene una serie de características que los modelos de subclases no admiten.\n",
        "\n",
        "Sin embargo, la subclasificación de modelos le brinda una mayor flexibilidad al crear modelos que no se pueden expresar fácilmente como gráficos acíclicos dirigidos de capas (por ejemplo, no podría implementar un Tree-RNN con la API funcional, tendría que subclasificar `Model` directamente).\n",
        "\n",
        "\n",
        "### Estas son las fortalezas de la API funcional:\n",
        "\n",
        "Las propiedades enumeradas a continuación también son ciertas para los modelos secuenciales (que también son estructuras de datos), pero no son ciertas para los modelos subclasificados (que son bytecode de Python, no estructuras de datos).\n",
        "\n",
        "\n",
        "#### Es menos detallado.\n",
        "\n",
        "No ` super (MyClass, self) .__ init __ (...)`, no `def call (self, ...): `, etc.\n",
        "\n",
        "Comparar:\n",
        "\n",
        "```pitón\n",
        "input = keras.Input (shape = (32,))\n",
        "x = capas. Denso (64, activación = 'relu') (entradas)\n",
        "salidas = capas. Denso (10) (x)\n",
        "mlp = keras.Model (entradas, salidas)\n",
        "```\n",
        "\n",
        "Con la versión subclaseada:\n",
        "\n",
        "```pitón\n",
        "clase MLP (keras.Model):\n",
        "\n",
        "  def __init __ (self, ** kwargs):\n",
        "    super (MLP, self) .__ init __ (** kwargs)\n",
        "    self.dense_1 = capas.Dense (64, activación = 'relu')\n",
        "    self.dense_2 = layers.Dense (10)\n",
        "\n",
        "  llamada def (auto, entradas):\n",
        "    x = self.dense_1 (entradas)\n",
        "    return self.dense_2 (x)\n",
        "\n",
        "# Instanciar el modelo.\n",
        "mlp = MLP ()\n",
        "# Necesario para crear el estado del modelo.\n",
        "# El modelo no tiene un estado hasta que se llama al menos una vez.\n",
        "_ = mlp (tf.zeros ((1, 32)))\n",
        "```\n",
        "\n",
        "\n",
        "#### Valida su modelo mientras lo está definiendo.\n",
        "\n",
        "En la API funcional, su especificación de entrada (forma y dtype) se crea de antemano (a través de `Input`), y cada vez que llama a una capa, la capa comprueba que la especificación que se le pasa coincide con sus supuestos, y generará un mensaje de error útil si no.\n",
        "\n",
        "Esto garantiza que se ejecutará cualquier modelo que pueda construir con la API funcional. Toda la depuración (que no sea la depuración relacionada con la convergencia) ocurrirá estáticamente durante la construcción del modelo, y no en el momento de la ejecución. Esto es similar a la comprobación de tipo en un compilador.\n",
        "\n",
        "\n",
        "#### Tu modelo funcional es trazable e inspeccionable.\n",
        "\n",
        "Puedes trazar el modelo como un gráfico, y puedes acceder fácilmente a los nodos intermedios en este gráfico, por ejemplo, para extraer y reutilizar las activaciones de las capas intermedias, como vimos en un ejemplo anterior:\n",
        "\n",
        "```pitón\n",
        "features_list = [layer.output para la capa en vgg19.layers]\n",
        "feat_extraction_model = keras.Model (input = vgg19.input, salidas = features_list)\n",
        "```\n",
        "\n",
        "\n",
        "#### Su modelo funcional puede ser serializado o clonado.\n",
        "\n",
        "Debido a que un modelo funcional es una estructura de datos en lugar de un fragmento de código, es serializable de forma segura y se puede guardar como un único archivo que le permite recrear exactamente el mismo modelo sin tener acceso a ninguno de los códigos originales. Consulta nuestra [guía de guardado y serialización] (./save_and_serialize.ipynb) para obtener más detalles.\n",
        "\n",
        "\n",
        "### Estas son las debilidades de la API funcional:\n",
        "\n",
        "\n",
        "#### No admite arquitecturas dinámicas.\n",
        "\n",
        "La API funcional trata los modelos como DAG de capas. Esto es cierto para la mayoría de las arquitecturas de aprendizaje profundo, pero no para todas: por ejemplo, las redes recursivas o los RNN de árbol no siguen este supuesto y no se pueden implementar en la API funcional.\n",
        "\n",
        "\n",
        "#### A veces, solo necesitas escribir todo desde cero.\n",
        "\n",
        "Al escribir actividades avanzadas, es posible que desee hacer cosas que están fuera del alcance de \"definir un DAG de capas\": por ejemplo, es posible que desee exponer múltiples métodos personalizados de entrenamiento e inferencia en su instancia de modelo. Esto requiere subclases.\n",
        "\n",
        "\n",
        "---\n",
        "\n",
        "\n",
        "Para profundizar más en las diferencias entre la API funcional y la subclasificación de modelos, puede leer [¿Qué son las API simbólicas e imperativas en TensorFlow 2.0?] (Https://medium.com/tensorflow/what-are-symbolic-and -imperative-apis-in-tensorflow-2-0-dfccecb01021)."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "Ym1jrCqusGvj"
      },
      "source": [
        "## Mezcla y combina diferentes estilos de API\n",
        "\n",
        "Es importante destacar que elegir entre la subclasificación de API funcional o modelo no es una decisión binaria que lo restringe a una categoría de modelos. Todos los modelos en la API tf.keras pueden interactuar con cada uno, ya sean modelos secuenciales, modelos funcionales o modelos / capas subclasificados escritos desde cero.\n",
        "\n",
        "Siempre puede usar un modelo funcional o modelo secuencial como parte de un modelo / capa subclasificado:"
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "colab_type": "code",
        "id": "9zF5YTLy_vGZ",
        "colab": {}
      },
      "source": [
        "units = 32\n",
        "timesteps = 10\n",
        "input_dim = 5\n",
        "\n",
        "# Define a Functional model\n",
        "inputs = keras.Input((None, units))\n",
        "x = layers.GlobalAveragePooling1D()(inputs)\n",
        "outputs = layers.Dense(1, activation='sigmoid')(x)\n",
        "model = keras.Model(inputs, outputs)\n",
        "\n",
        "\n",
        "class CustomRNN(layers.Layer):\n",
        "\n",
        "  def __init__(self):\n",
        "    super(CustomRNN, self).__init__()\n",
        "    self.units = units\n",
        "    self.projection_1 = layers.Dense(units=units, activation='tanh')\n",
        "    self.projection_2 = layers.Dense(units=units, activation='tanh')\n",
        "    # Our previously-defined Functional model\n",
        "    self.classifier = model\n",
        "\n",
        "  def call(self, inputs):\n",
        "    outputs = []\n",
        "    state = tf.zeros(shape=(inputs.shape[0], self.units))\n",
        "    for t in range(inputs.shape[1]):\n",
        "      x = inputs[:, t, :]\n",
        "      h = self.projection_1(x)\n",
        "      y = h + self.projection_2(state)\n",
        "      state = y\n",
        "      outputs.append(y)\n",
        "    features = tf.stack(outputs, axis=1)\n",
        "    print(features.shape)\n",
        "    return self.classifier(features)\n",
        "\n",
        "rnn_model = CustomRNN()\n",
        "_ = rnn_model(tf.zeros((1, timesteps, input_dim)))"
      ],
      "execution_count": 0,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "oxW1d0a8_ufg"
      },
      "source": [
        "Inversamente, puede usar cualquier Capa o Modelo subclasificado en la API Funcional siempre que implemente un método `call` que siga uno de los siguientes patrones:\n",
        "\n",
        "- `call (self, input, ** kwargs)` donde `input` es un tensor o una estructura anidada de tensores (por ejemplo, una lista de tensores), y donde` ** kwargs` son argumentos no tensoriales (no input )\n",
        "- `call (self, input, training = None, ** kwargs)` donde `training` es un valor booleano que indica si la capa debe comportarse en modo de entrenamiento y modo de inferencia.\n",
        "- `call (self, input, mask = None, ** kwargs)` donde `mask` es un tensor de máscara booleano (útil para RNN, por ejemplo).\n",
        "- `call (self, input, training = None, mask = None, ** kwargs)` - por supuesto, puede tener tanto un comportamiento específico de enmascaramiento como de entrenamiento al mismo tiempo.\n",
        "\n",
        "Además, si implementa el método `get_config` en su Capa o Modelo personalizado, los modelos funcionales que cree con él seguirán siendo serializables y clonables.\n",
        "\n",
        "Aquí hay un ejemplo rápido en el que usamos un RNN personalizado escrito desde cero en un modelo funcional:"
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "colab_type": "code",
        "id": "TmTEZ6F3ArJR",
        "colab": {}
      },
      "source": [
        "units = 32\n",
        "timesteps = 10\n",
        "input_dim = 5\n",
        "batch_size = 16\n",
        "\n",
        "\n",
        "class CustomRNN(layers.Layer):\n",
        "\n",
        "  def __init__(self):\n",
        "    super(CustomRNN, self).__init__()\n",
        "    self.units = units\n",
        "    self.projection_1 = layers.Dense(units=units, activation='tanh')\n",
        "    self.projection_2 = layers.Dense(units=units, activation='tanh')\n",
        "    self.classifier = layers.Dense(1, activation='sigmoid')\n",
        "\n",
        "  def call(self, inputs):\n",
        "    outputs = []\n",
        "    state = tf.zeros(shape=(inputs.shape[0], self.units))\n",
        "    for t in range(inputs.shape[1]):\n",
        "      x = inputs[:, t, :]\n",
        "      h = self.projection_1(x)\n",
        "      y = h + self.projection_2(state)\n",
        "      state = y\n",
        "      outputs.append(y)\n",
        "    features = tf.stack(outputs, axis=1)\n",
        "    return self.classifier(features)\n",
        "\n",
        "# Tenga en cuenta que especificamos un tamaño de lote estático para las entradas con `batch_shape`\n",
        "# arg, porque el cálculo interno de `CustomRNN` requiere un tamaño de lote estático\n",
        "# (cuando creamos el tensor de ceros `estado`).\n",
        "inputs = keras.Input(batch_shape=(batch_size, timesteps, input_dim))\n",
        "x = layers.Conv1D(32, 3)(inputs)\n",
        "outputs = CustomRNN()(x)\n",
        "\n",
        "model = keras.Model(inputs, outputs)\n",
        "\n",
        "rnn_model = CustomRNN()\n",
        "_ = rnn_model(tf.zeros((1, 10, 5)))"
      ],
      "execution_count": 0,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "6VxcYb4qArlb"
      },
      "source": [
        "¡Esto concluye nuestra guía sobre la API funcional \"Keras\"!\n",
        "\n",
        "Ahora tienes a tu alcance un poderoso conjunto de herramientas para construir modelos de aprendizaje profundo."
      ]
    }
  ]
}